#ifndef COMMAND_H_
#define COMMAND_H_

#include <list>
#include <boost/ptr_container/ptr_list.hpp>

#include "conf.h"
#include "shellwords.h"
#include "loader.h"

namespace gpyp {

enum ProcessType {
  PROCESS_TYPE_NORMAL,
  PROCESS_TYPE_PLAY,
  PROCESS_TYPE_RECORD,
  PROCESS_TYPE_SPECIAL_PEERCAST,
  PROCESS_TYPE_SPECIAL_PCRAW_PROXY,
};

class Process : boost::noncopyable {
private:
  sigc::signal<void> signal_run_;
  sigc::signal<void> signal_dead_;
  sigc::signal<void> signal_error_;

  typedef sigc::signal<void, const std::string &> SignalOutput;
  SignalOutput signal_stdout_;
  SignalOutput signal_stderr_;

public:
  inline sigc::signal<void> signal_run() {
    return signal_run_;
  }
  inline sigc::signal<void> signal_dead() {
    return signal_dead_;
  }
  inline sigc::signal<void> signal_error() {
    return signal_error_;
  }

  inline SignalOutput signal_stdout() {
    return signal_stdout_;
  }
  inline SignalOutput signal_stderr() {
    return signal_stderr_;
  }

private:
  Application      &app_;
  ProcessType       type_;
  Glib::ustring     command_;
  Glib::ustring     name_;
  RefChannel        channel_;

  Glib::ustring     error_message_;

  GPid              pid_;
  int               status_;
  bool              signaled_;

  guint             watch_proc_;

  gint              fd_stdin_;
  gint              fd_stdout_;
  gint              fd_stderr_;

  GIOChannel       *ioc_stdout_;
  GIOChannel       *ioc_stderr_;

  guint             watch_stdout_;
  guint             watch_stderr_;

  std::string       buf_stdout_;
  std::string       buf_stderr_;

  boost::posix_time::ptime time_start_;
  boost::posix_time::ptime time_end_;

private:
  static void spawn_setup(gpointer data);

  static gboolean _on_stdout(GIOChannel  *source,
                             GIOCondition condition,
                             Process     *proc);
  static gboolean _on_stderr(GIOChannel  *source,
                             GIOCondition condition,
                             Process     *proc);
  void on_read_buf(GIOChannel   *source,
                   std::string  &buf,
                   SignalOutput &signal);

  static void _on_dead(GPid pid, gint status, gpointer data);
  bool on_dead();

  void on_error(const Glib::ustring &message);

  void clear_data(bool all);

public:
  bool run();

  void terminate();
  void kill();
  void signal(int sig);

  inline const Glib::ustring &get_error_message() const {
    return error_message_;
  }

  inline pid_t get_pid() const {
    return pid_;
  }
  inline bool alive() const {
    return pid_ != 0;
  }

  bool status_exited();
  int  status_exitstatus();
  bool signaled();
  bool signal_sent();
  bool aborted();

  inline ProcessType get_type() const {
    return type_;
  }
  inline const Glib::ustring &get_command() const {
    return command_;
  }
  inline const Glib::ustring &get_name() const {
    return name_;
  }
  inline RefChannel get_channel() {
    return channel_;
  }

  inline boost::posix_time::ptime get_time_start() {
    return time_start_;
  }
  inline boost::posix_time::ptime get_time_end() {
    return time_end_;
  }
  boost::posix_time::time_duration get_uptime();

  Process(Application         &app,
          ProcessType          type,
          const Glib::ustring &command,
          const Glib::ustring &name,
          RefChannel           channel = RefChannel());
  virtual ~Process();
};
typedef boost::shared_ptr<Process> RefProcess;

class Recorder : boost::noncopyable {
private:
  sigc::signal<void, RefProcess> signal_new_process_;
  sigc::signal<void> signal_stop_;

public:
  inline sigc::signal<void, RefProcess> signal_new_process() {
    return signal_new_process_;
  }
  inline sigc::signal<void> signal_stop() {
    return signal_stop_;
  }

private:
  Application  &app_;
  RefChannel    ch_;
  RefProcess    proc_;
  int           log_fd_;

  bool          stopped_;

  sigc::connection conn_run_;
  sigc::connection conn_error_;
  sigc::connection conn_stdout_;
  sigc::connection conn_stderr_;
  sigc::connection conn_dead_;

  time_t       time_started_;
  size_t       dead_count_;

  Glib::ustring filename_;

private:
  bool create_filename();

  void write_log(const std::string &message);

  void proc_on_run();
  void proc_on_error();
  void proc_on_stdout(const std::string &message);
  void proc_on_stderr(const std::string &message);
  void proc_on_dead();
  bool proc_on_dead_idle();
  bool run_new_process();

protected:
  virtual void on_stop();

public:
  void start();

  inline RefChannel get_channel() {
    return ch_;
  }
  inline RefProcess get() {
    return proc_;
  }

  inline bool is_stopped() const {
    return stopped_;
  }

  inline const Glib::ustring &get_filename() const {
    return filename_;
  }

  Recorder(Application &app, RefChannel ch);
  virtual ~Recorder();
};
typedef boost::shared_ptr<Recorder> RefRecorder;

class Application;
class YellowPage;
typedef boost::shared_ptr<YellowPage> RefYellowPage;
class Channel;
typedef boost::shared_ptr<Channel> RefChannel;
class Favorite;
typedef boost::shared_ptr<Favorite> RefFavorite;
class Rule;
typedef boost::shared_ptr<Rule> RefRule;
namespace peercast {
class RelayedChannel;
typedef boost::shared_ptr<RelayedChannel> RefRelayedChannel;
class Servent;
typedef boost::shared_ptr<Servent> RefServent;
}

class SearchBox;
class TextEditor;
class WebView;

class VariableName;

struct CommandOpts : boost::noncopyable {
  const char                   *text;
  RefYellowPage                 yp;
  RefChannel                    ch;
  RefFavorite                   favorite;
  RefRule                       rule;
  RefProcess                    proc;
  peercast::RefRelayedChannel   relayed_ch;
  peercast::RefServent          servent;
  SearchBox                    *search;
  TextEditor                   *text_editor;
  WebView                      *webview;
  ConfMain::RefPlayer           player;
  const char                   *command;
  const char                   *url;
  const char                   *filename;
  const char                   *dir;

  inline CommandOpts() :
    text       (NULL),
    search     (NULL),
    text_editor(NULL),
    webview    (NULL),
    command    (NULL),
    url        (NULL),
    filename   (NULL),
    dir        (NULL)
  {
  }
};

class BuiltinCommand : boost::noncopyable {
public:
  typedef sigc::slot<bool, ShellLine &, CommandOpts &> CMDSlot;
  typedef sigc::slot<void, const Glib::ustring &, BuiltinCommand &> EnumSlot;

protected:
  struct EnumData {
    Glib::ustring prefix;
    EnumSlot      slot;
  };

private:
  Glib::ustring name_;
  CMDSlot       cb_cmd_;

  Glib::ustring help_string_;

  GHashTable   *bc_hash_;

private:
  static void enum_commands_hash(const char *name, BuiltinCommand *value, EnumData *data);
  static void value_destroy_func(BuiltinCommand *value);
  inline static GHashTable *create_hash_table() {
    return g_hash_table_new_full(g_str_hash,
                                 g_str_equal,
                                 NULL,
                                 (GDestroyNotify)&BuiltinCommand::value_destroy_func);
  }

public:
  BuiltinCommand *add_ns(const Glib::ustring &name);
  BuiltinCommand *add_cmd(const Glib::ustring &name,
                          CMDSlot              cb_cmd,
                          const Glib::ustring &help_string);

  void remove(BuiltinCommand &bc);

  bool run(VariableName &name,
           ShellLine    &line,
           CommandOpts  &opts) const;

  void enum_commands(EnumSlot slot);

  inline const Glib::ustring &get_help_string() const {
    return help_string_;
  }

  BuiltinCommand();
protected:
  BuiltinCommand(const Glib::ustring &name);
  BuiltinCommand(const Glib::ustring &name,
                 CMDSlot              cb_cmd,
                 const Glib::ustring &help_string);
public:
  ~BuiltinCommand();
};

template<typename T>
class IDHashTable;

class CommandRunner : boost::noncopyable {
  friend class Recorder;
private:
  sigc::signal<void, RefProcess> signal_new_process_;
  sigc::signal<void, RefProcess> signal_run_peercast_;
  sigc::signal<void, RefProcess> signal_run_pcraw_proxy_;
  sigc::signal<void, RefChannel> signal_play_;
  sigc::signal<void, RefRecorder> signal_record_;
  sigc::signal<void, RefProcess> signal_aborted_;
public:
  inline sigc::signal<void, RefProcess> signal_new_process() {
    return signal_new_process_;
  }
  inline sigc::signal<void, RefProcess> signal_run_peercast() {
    return signal_run_peercast_;
  }
  inline sigc::signal<void, RefProcess> signal_run_pcraw_proxy() {
    return signal_run_pcraw_proxy_;
  }
  inline sigc::signal<void, RefChannel> signal_play() {
    return signal_play_;
  }
  inline sigc::signal<void, RefRecorder> signal_record() {
    return signal_record_;
  }
  inline sigc::signal<void, RefProcess> signal_aborted() {
    return signal_aborted_;
  }

public:
  typedef ConfMain::RefPlayer RefPlayer;

  struct ProcessContext : boost::noncopyable {
    RefProcess       proc_;
    sigc::connection conn_dead_;
    sigc::connection conn_error_;

    inline ProcessContext(Process *proc) :
      proc_(proc)
    {
    }
    inline ~ProcessContext() {
      conn_dead_.disconnect();
      conn_error_.disconnect();
    }
  };
  struct RecorderContext : boost::noncopyable {
    RefRecorder      recorder_;
    sigc::connection conn_stop_;

    inline RecorderContext(Recorder *recorder) :
      recorder_(recorder)
    {
    }
    inline ~RecorderContext() {
      conn_stop_.disconnect();
    }
  };
  typedef boost::ptr_list<ProcessContext>  ProcessList;
  typedef boost::ptr_list<RecorderContext> RecorderList;

private:
  Application &app_;

  BuiltinCommand &bc_root_;
  BuiltinCommand &bc_proc_;
  BuiltinCommand &bc_search_;
  BuiltinCommand &bc_text_editor_;

  RefProcess   proc_peercast_;
  RefProcess   proc_pcraw_proxy_;

  ProcessList  processes_;
  RecorderList recorders_;

private:
  void shell_escape(Glib::ustring &str);

  bool test_pid(const Glib::ustring &name);

  bool main_on_quit();

public:
  void run_peercast(bool check_prev = false);
  void run_pcraw_proxy(bool check_prev = false);

private:
  void on_aborted(RefProcess proc);

  void peercast_on_dead();
  void peercast_on_error();
  void pcraw_proxy_on_dead();
  void pcraw_proxy_on_error();

  void remove_process(ProcessContext *pc);
  void remove_recorder(RecorderContext *rc);

  void process_dead_message(RefProcess proc);
  void process_error_message(RefProcess proc);
  void process_on_dead(ProcessContext *pc);
  void process_on_error(ProcessContext *pc);

  void recorder_on_stop(RecorderContext *rc);

public:
  bool format(const Glib::ustring &command,
              Glib::ustring       &res,
              CommandOpts         *opts);

  void run(const Glib::ustring &command,
           CommandOpts         *opts = NULL,
           ProcessType          type = PROCESS_TYPE_NORMAL);

  RefProcess peercast();
  RefProcess pcraw_proxy();

  bool play(RefChannel ch, RefPlayer player = RefPlayer());
  bool auto_play(RefChannel ch, RefPlayer player = RefPlayer());

  void webbrowser(const Glib::ustring &url);
  void bbsbrowser(const Glib::ustring &url);

  bool record(RefChannel ch);
  bool auto_record(RefChannel ch);

  inline const ProcessList &get_processes() const {
    return processes_;
  }
  inline const RecorderList &get_recorders() const {
    return recorders_;
  }

private:
  bool bc_peercast      (ShellLine &line, CommandOpts &opts);
  bool bc_pcraw_proxy   (ShellLine &line, CommandOpts &opts);
  bool bc_terminal      (ShellLine &line, CommandOpts &opts);
  bool bc_play          (ShellLine &line, CommandOpts &opts);
  bool bc_auto_play     (ShellLine &line, CommandOpts &opts);
  bool bc_record        (ShellLine &line, CommandOpts &opts);
  bool bc_auto_record   (ShellLine &line, CommandOpts &opts);
  bool bc_webbrowser    (ShellLine &line, CommandOpts &opts);
  bool bc_bbsbrowser    (ShellLine &line, CommandOpts &opts);
  bool bc_copy          (ShellLine &line, CommandOpts &opts);
  bool bc_request_url   (ShellLine &line, CommandOpts &opts);
  bool bc_quit          (ShellLine &line, CommandOpts &opts);

  bool bc_proc_terminate(ShellLine &line, CommandOpts &opts);
  bool bc_proc_kill     (ShellLine &line, CommandOpts &opts);
  bool bc_proc_signal   (ShellLine &line, CommandOpts &opts);
  bool bc_proc_detach   (ShellLine &line, CommandOpts &opts);

  bool bc_search_show   (ShellLine &line, CommandOpts &opts);
  bool bc_search_hide   (ShellLine &line, CommandOpts &opts);
  bool bc_search_toggle (ShellLine &line, CommandOpts &opts);
  bool bc_search_clear  (ShellLine &line, CommandOpts &opts);

public:
  CommandRunner(Application &app, BuiltinCommand &bc_root);
};

}/*gpyp*/

#endif/*COMMAND_H_*/
